package appml

import (
	"log"
	"os"
	"path/filepath"
	"runtime"
	"slices"
	"strings"

	"github.com/shirou/gopsutil/v3/process"
)

// 进程信息
type Process struct {
	name   string   // 应用名称
	work   string   // 工作目录
	params []string // 参数，params[0] 就是 exe 的完整路径

	processes map[int32]*process.Process // 进程实例列表
}

// 创建进程信息。args...[0] 就是为可执行文件或命令
func NewProcess(name, work string, args ...string) (*Process, error) {
	if len(args) <= 0 {
		return nil, NewErr(runtime.Caller(0)).Msg("params to call NewProcess is empty")
	}
	exe, err := filepath.Abs(filepath.FromSlash(filepath.Clean(CleanCmdArg(args[0])))) // 标准化
	if err != nil {
		return nil, NewErr(runtime.Caller(0)).Err(err)
	}
	params := make([]string, 0, len(args))
	params = append(params, exe)
	for i, s := 1, len(args); i < s; i++ {
		if arg := CleanCmdArg(args[i]); arg != "" { // 去除空参数
			params = append(params, arg)
		}
	}
	return &Process{name: strings.TrimSpace(name), work: work, params: params}, nil
}

// 检查进程是否正在运行，如果未运行，则尝试启动
func (p *Process) StartIfNotRunning() error {
	running, err := p.IsRunning()
	if err != nil {
		return NewErr(runtime.Caller(0)).Err(err)
	}
	if running {
		return nil
	}
	return NewErr(runtime.Caller(0)).If(p.Start()) // 嵌套 *Err 实现类调用堆栈效果
}

// 判断进程是否正在运行
func (p *Process) IsRunning() (running bool, err error) {
	if p == nil {
		err = NewErr(runtime.Caller(0)).Msg("nil *ProcessInfo")
		return
	}
	if len(p.processes) == 0 {
		if err = p.FindAndBind(); err != nil {
			err = NewErr(runtime.Caller(0)).Err(err)
			return
		}
	}
	if len(p.processes) == 0 {
		return
	}
	err = MapDelete(p.processes, func(id int32, proc *process.Process) (bool, error) {
		if proc == nil {
			return true, nil
		}
		if r, e := proc.IsRunning(); e != nil {
			return false, NewErr(runtime.Caller(0)).Err(e) // 出错的，跳过
		} else if !r {
			return true, nil // 未运行的进程，删除掉
		} else {
			running = true // 有存活的进程
		}
		return false, nil
	}, nil)
	return
}

// 判断本程序的进程列表中，除 pids 之外是否还有其它的进程。
func (p *Process) ExistsExcept(pids ...int32) bool {
	c := len(p.processes)
	for _, pid := range pids {
		if _, ok := p.processes[pid]; ok {
			c--
		}
	}
	return c > 0
}

// 从 OS 的进程列表中与自身 Params 相匹配的进程。如果找到关联该进程。
func (p *Process) FindAndBind() error {
	items, err := process.Processes()
	if err != nil {
		return NewErr(runtime.Caller(0)).Err(err)
	}
	return p.FindAndBindFrom(items)
}

// 从指定的进程列表中与自身 Params 相匹配的进程。如果找到关联该进程。
func (p *Process) FindAndBindFrom(procs []*process.Process) error {
	for _, proc := range procs {
		cmdline, err := proc.Cmdline() // 包含命令本身
		if err != nil {
			continue
		}
		args, err := ParseCmdLine(cmdline)
		if err != nil || len(args) <= 0 {
			continue
		}
		if !slices.Equal(args, p.params) {
			continue
		}
		if p.processes == nil {
			p.processes = make(map[int32]*process.Process)
		}
		p.processes[proc.Pid] = proc
	}
	return nil
}

// 启动进程
func (p *Process) Start() error {
	log.Printf("process starting: %s", strings.Join(p.params, " "))
	pa := &os.ProcAttr{Dir: p.work, Env: os.Environ(), Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}
	proc, err := os.StartProcess(p.params[0], p.params, pa)
	if err != nil {
		return NewErr(runtime.Caller(0)).Err(err)
	}
	if p.processes == nil {
		p.processes = make(map[int32]*process.Process)
	}
	pid := int32(proc.Pid)
	p.processes[pid], err = process.NewProcess(pid)
	if err == nil {
		log.Printf("process started: [%d] %s", pid, strings.Join(p.params, " "))
	}
	return NewErr(runtime.Caller(0)).If(err)
}

// 杀死进程
func (p *Process) Kill() error {
	return MapDelete(p.processes, func(k int32, v *process.Process) (bool, error) {
		if v == nil {
			return true, nil
		}
		if running, err := v.IsRunning(); err == nil && !running {
			return true, nil
		}
		return true, NewErr(runtime.Caller(0)).If(v.Kill())
	}, nil)
}
